package com.zy.multiredis.config;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.JedisClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.StringUtils;

import com.zy.multiredis.util.MultiRedisInfoHolder;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
@ConditionalOnClass(MultiRedisConnectionFactoryMap.class)
public class MultiRedisConnectionConfig extends AbstractRedisConnectionConfiguration {

    private final ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers;

    private Environment environment;
    
    private static final String DEFAULT_REDIS_PROPERTY = "redis.default";
    
    protected MultiRedisConnectionConfig(ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
        ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
        ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers, Environment environment) {
        super(sentinelConfigurationProvider, clusterConfigurationProvider);
        this.environment = environment;
        this.builderCustomizers = builderCustomizers;
    }
    
    @Bean("default_redisconnectionfactory")
    @Primary
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public RedisConnectionFactory configDefaultFactory() {
        RedisProperties redisProperties =
            Binder.get(environment).bind(DEFAULT_REDIS_PROPERTY, Bindable.of(RedisProperties.class)).get();
        JedisConnectionFactory jedisConnectionFactory = createJedisConnectionFactory(redisProperties);
        return jedisConnectionFactory;
    }

    @Bean
    @ConditionalOnMissingBean(MultiRedisConnectionFactoryMap.class)
    public MultiRedisConnectionFactoryMap configJedis() {
        Map<String, RedisProperties> map = MultiRedisInfoHolder.getmultiRedisInfos();
        Map<String, RedisConnectionFactory> redisConnectionFactories = new LinkedHashMap<>();
        for (Map.Entry<String, RedisProperties> entry : map.entrySet()) {
            // 获取到每一个redis配置
            RedisProperties redisProperties = entry.getValue();
            // 创建redisfactory
            JedisConnectionFactory jedisConnectionFactory = createJedisConnectionFactory(redisProperties);
            redisConnectionFactories.put(entry.getKey(), jedisConnectionFactory);
        }
        return new MultiRedisConnectionFactoryMap(redisConnectionFactories);
    }

    private JedisConnectionFactory createJedisConnectionFactory(RedisProperties redisProperties) {
        JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(redisProperties);
        if (getSentinelConfig(redisProperties) != null) {
            return new JedisConnectionFactory(getSentinelConfig(redisProperties), clientConfiguration);
        }
        if (getClusterConfiguration(redisProperties) != null) {
            return new JedisConnectionFactory(getClusterConfiguration(redisProperties), clientConfiguration);
        }
        return new JedisConnectionFactory(getStandaloneConfig(redisProperties), clientConfiguration);
    }

    private JedisClientConfiguration getJedisClientConfiguration(RedisProperties properties) {
        JedisClientConfigurationBuilder builder = applyProperties(properties, JedisClientConfiguration.builder());
        RedisProperties.Pool pool = properties.getJedis().getPool();
        if (pool != null) {
            applyPooling(pool, builder);
        }
        if (StringUtils.hasText(properties.getUrl())) {
            customizeConfigurationFromUrl(properties, builder);
        }
        customize(builder);
        return builder.build();
    }

    private JedisClientConfigurationBuilder applyProperties(RedisProperties properties,
        JedisClientConfigurationBuilder builder) {
        if (properties.isSsl()) {
            builder.useSsl();
        }
        if (properties.getTimeout() != null) {
            Duration timeout = properties.getTimeout();
            builder.readTimeout(timeout).connectTimeout(timeout);
        }
        return builder;
    }

    private void applyPooling(RedisProperties.Pool pool,
        JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
        builder.usePooling().poolConfig(jedisPoolConfig(pool));
    }

    private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(pool.getMaxActive());
        config.setMaxIdle(pool.getMaxIdle());
        config.setMinIdle(pool.getMinIdle());
        if (pool.getMaxWait() != null) {
            config.setMaxWaitMillis(pool.getMaxWait().toMillis());
        }
        return config;
    }

    private void customizeConfigurationFromUrl(RedisProperties properties,
        JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
        ConnectionInfo connectionInfo = parseUrl(properties.getUrl());
        if (connectionInfo.isUseSsl()) {
            builder.useSsl();
        }
    }

    private void customize(JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
        this.builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
    }

}
